﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;

using Newtonsoft.Json;

namespace RVNetworkLib
{
    /* Room Discovery Protocol
     * 
     * Using in local area network to find created room list.
     */
    public class RDP
    {
        public delegate void NewRoomHandler(LanRoomInfo roomInfo);

        private System.Windows.Forms.Timer _searchTimer;
        private UdpClient _rdpListener;
        private List<LanRoomInfo> _roomList;
        private string _ip;

        public NewRoomHandler NewRoom { get; set; }

        public RDP(int searchTimeout)
        {
            _searchTimer = new System.Windows.Forms.Timer();
            _searchTimer.Enabled = true;
            _searchTimer.Interval = searchTimeout;
            _searchTimer.Tick += new System.EventHandler(StopSearch);

            _ip = Utility.MyIP();

            _roomList = new List<LanRoomInfo>();
        }

        public void Close()
        {
            _searchTimer.Tick -= StopSearch;
            _searchTimer = null;
            _roomList = null;
        }

        public List<LanRoomInfo> QueryRoom()
        {
            _rdpListener = new UdpClient();
            _rdpListener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            _rdpListener.ExclusiveAddressUse = false;
            _rdpListener.Client.Bind(new IPEndPoint(IPAddress.Any, NetworkEnv.CLIENT_PORT));

            // Clear cached
            _roomList.Clear();

            // Create receiver thread
            Thread searchWorker = new Thread(() => receiveMsg());
            searchWorker.Start();

            // Send query room list command
            SendQueryCommand();

            searchWorker.Join();

            return _roomList;
        }

        private void SendQueryCommand()
        {
            Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            IPEndPoint iep1 = new IPEndPoint(IPAddress.Broadcast, NetworkEnv.SERVER_PORT);

            Packet queryPacket = new Packet();
            queryPacket.MagicNumber = 0;
            queryPacket.CheckSum = 0;
            queryPacket.Type = PacketType.LQUERY_ROOM_LIST;
            queryPacket.UserID = 0;
            queryPacket.DataLength = 0;
            queryPacket.Payload = null;

            sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            sock.SendTo(queryPacket.Serialize(), iep1);
            sock.Shutdown(SocketShutdown.Both);
            sock.Close();
        }

        private void receiveMsg()
        {
            if (_rdpListener == null) return;

            IPEndPoint iep = new IPEndPoint(IPAddress.Any, NetworkEnv.CLIENT_PORT);

            try
            {
                while (true)
                {
                    byte [] data = _rdpListener.Receive(ref iep);

                    Packet ph = Packet.Deserialize(data);
                    if (ph == null) continue;

                    if (ph.Type == PacketType.LQUERY_ROOM_LIST_R)
                    {
                        ParseMsg(ph.Payload);
                    }
                }
            }
            catch (System.Net.Sockets.SocketException)
            {
                System.Diagnostics.Trace.WriteLine("Search Timedout");
            }
        }

        private void ParseMsg(byte[] payload)
        {
            LanRoomInfo lri = JsonConvert.DeserializeObject<LanRoomInfo>(Encoding.UTF8.GetString(payload));
            _roomList.Add(lri);
            if (NewRoom != null)
            {
                NewRoom(lri);
            }
        }

        private void StopSearch(object sender, System.EventArgs e)
        {
            _rdpListener.Close();
            _searchTimer.Enabled = false;
        }
    }
}
